#include "LogoExecutorNoWnd.h"
#include "icanvas.h"
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <string.h>

#define PI                       3.1415927
#define ROUND(d)      (int)(d >= 0 ? d+0.5 : d-0.5)
#define MIN(a,b)      (a<b ? a: b)


bool g_bUp2Down = false;
bool g_bLeft2Right = false;
int ComparePoints(const void* p0, const void* p1);
int IntDiv(int a, int b);

#define IntMod(a,b) (a >= 0 ? a%b : b - (-a)%b)

CLogoExecutorNoWnd::CLogoExecutorNoWnd()
{
   m_pStrokeHead = NULL;
   m_pStrokeTail = NULL;
   m_pCanvas = NULL;

   reset();
}

CLogoExecutorNoWnd::~CLogoExecutorNoWnd()
{
   RemoveAllStrokes();
}

void CLogoExecutorNoWnd::SetCanvas(ICanvas* pCanvas)
{
   m_pCanvas = pCanvas;
}

void CLogoExecutorNoWnd::DrawAllStrokes(RECT* pRect)
{
   assert (m_pCanvas);
   CStroke* stk = m_pStrokeHead;
   while (stk) {
      if (pRect && !stk->Intersect(m_pCanvas, *pRect)) {
         stk = stk->m_pNext;
         continue;
      }
      m_pCanvas->DrawStroke(stk);
      stk = stk->m_pNext;
   }
}

void CLogoExecutorNoWnd::canvassize (double x, double y)
{
   m_uCanvasWidth = (x < 1 ? DEFAULT_CANVAS_WIDTH : ROUND(x));
   m_uCanvasHeight = (y < 1 ? DEFAULT_CANVAS_HEIGHT : ROUND(y));
}

void CLogoExecutorNoWnd::reset()
{
   m_uCanvasWidth       =     DEFAULT_CANVAS_WIDTH;
   m_uCanvasHeight      =     DEFAULT_CANVAS_HEIGHT;
   m_clrPen             =     DEFAULT_PEN_COLOR;
   m_clrCanvas          =     DEFAULT_CANVAS_COLOR;
   m_direction          =     DEFAULT_DIRECTION;
   m_bShowTurtle        =     DEFAULT_SHOW;
   m_bWrap              =     DEFAULT_WRAP;
   m_uPenWidth          =     DEFAULT_PEN_WIDTH;
   m_bPenDown           =     DEFAULT_PEN_DOWN;
   m_uFontSize          =     DEFAULT_FONT_SIZE;
   m_uValidEnds         =     0;
   center ();
   RemoveAllStrokes();
}

void CLogoExecutorNoWnd::clear()
{
   RemoveAllStrokes();
}

void CLogoExecutorNoWnd::hide (void)
{
   m_bShowTurtle = false;
}

void CLogoExecutorNoWnd::canvascolor (double r, double g, double b)
{
   r = r < 0 ? 0 : r;
   g = g < 0 ? 0 : g;
   b = b < 0 ? 0 : b;
   m_clrCanvas = RGB (ROUND(r), ROUND(g), ROUND(b));
}

void CLogoExecutorNoWnd::pencolor (double r, double g, double b)
{
   r = r < 0 ? 0 : r;
   g = g < 0 ? 0 : g;
   b = b < 0 ? 0 : b;
   m_clrPen = RGB (ROUND(r), ROUND(g), ROUND(b));
}

void CLogoExecutorNoWnd::penwidth (double w)
{
   m_uPenWidth = (w < 1 ? DEFAULT_PEN_WIDTH: ROUND(w));
}

void CLogoExecutorNoWnd::go (double x, double y)
{
   gox (x);
   goy (y);
}

void CLogoExecutorNoWnd::direction (double w)
{
   m_direction = -w;
}

void CLogoExecutorNoWnd::turnright (double d)
{
   m_direction -= d;
}

void CLogoExecutorNoWnd::turnleft (double d)
{
   m_direction += d;
}

void CLogoExecutorNoWnd::forward (double d)
{
   int dx, dy;
   int x1, y1;
   DistanceToDelta(d, &dx, &dy);
   x1 = m_ptCurrent.x + dx;
   y1 = m_ptCurrent.y + dy;

   LineTo(x1, y1);
}

void CLogoExecutorNoWnd::backward (double d)
{
   int dx, dy;
   int x1, y1;
   DistanceToDelta(d, &dx, &dy);
   x1 = m_ptCurrent.x - dx;
   y1 = m_ptCurrent.y - dy;

   LineTo(x1, y1);
}

void CLogoExecutorNoWnd::fontsize (double n)
{
   n = n < 1 ? DEFAULT_FONT_SIZE : n;
   m_uFontSize = ROUND(n);
}

void CLogoExecutorNoWnd::print (char* str)
{
   assert (m_pCanvas);
   
   CStroke* stk = new CStroke;
   if (!stk) {
      exit (-1);
   }
   stk->CopyString (str);
   stk->m_text.position = m_ptCurrent;
   stk->m_text.fontsize = m_uFontSize;
   stk->m_text.pencolor = m_clrPen;
   AddToStrokeList(stk);
}

void CLogoExecutorNoWnd::penup (void)
{
   m_bPenDown = false;
}

void CLogoExecutorNoWnd::pendown (void)
{
   m_bPenDown = true;
}

void CLogoExecutorNoWnd::center()
{
   assert (m_uCanvasWidth > 0 && m_uCanvasHeight > 0);
   m_ptCurrent.x = m_uCanvasWidth/2;
   m_ptCurrent.y = m_uCanvasHeight/2;
}

void CLogoExecutorNoWnd::gox (double x)
{
   assert (m_uCanvasWidth > 0);

   m_ptCurrent.x = ROUND(x);
   if (!m_bWrap) {
      return;
   }

   m_ptCurrent.x = IntMod(m_ptCurrent.x, m_uCanvasWidth);
}

void CLogoExecutorNoWnd::goy (double y)
{
   assert (m_uCanvasHeight > 0);

   m_ptCurrent.y = ROUND(y);
   if (!m_bWrap) {
      return;
   }

   m_ptCurrent.y = IntMod(m_ptCurrent.y, m_uCanvasHeight);
}

void CLogoExecutorNoWnd::wrapon (void)
{
   m_bWrap = true;
}

void CLogoExecutorNoWnd::wrapoff (void)
{
   m_bWrap = false;
}

void CLogoExecutorNoWnd::show (void)
{
   m_bShowTurtle = true;
}

void CLogoExecutorNoWnd::AddToStrokeList(CStroke* pStk)
{
   assert (pStk);

   assert (m_pCanvas);
   m_pCanvas->DrawStroke(pStk);   
   m_pCanvas->DrawTurtle();
   if (m_pStrokeTail) {
      assert (m_pStrokeHead);
      m_pStrokeTail->m_pNext = pStk;
      m_pStrokeTail = pStk;
      return;
   }
   assert (!m_pStrokeHead);
   m_pStrokeHead = m_pStrokeTail = pStk;
}

void CLogoExecutorNoWnd::RemoveAllStrokes()
{
   CStroke* pCur = m_pStrokeHead; 
   while (pCur) {
      CStroke* pTmp = pCur->m_pNext;
      delete (pCur);
      pCur = pTmp;
   }
   m_pStrokeHead = m_pStrokeTail = NULL;
}

void CLogoExecutorNoWnd::LineTo (int x, int y)
{
   POINT ptTo;
   ptTo.x = x;
   ptTo.y = y;
   if (m_ptCurrent.x == x && m_ptCurrent.y == y) {
       return;
   }
   if (!m_bWrap) {
      AddLineStroke(&m_ptCurrent, &ptTo);
   }
   // m_bWrap is true.
   // The algorithm is: calculate the intersect points of the line to be drawn
   //  and the lines of:
   //     -- x = n * m_uCanvasWidth
   //     -- x = n * m_uCanvasWidth -1
   //     -- y = n * m_uCanvasHeight
   //     -- y = n * m_uCanvasHeight -1
   // Then we get an array of intersect points. 
   // Sorting this array, Remove same points, we will get Strokes.
   POINT pt0, pt1;
   pt0 = m_ptCurrent;
   pt1.x = x;
   pt1.y = y;

   int nYIntersects = GetIntersects(pt0.y, y, m_uCanvasHeight, m_nYCross, MAX_TRANVAS);
   int nXIntersects = GetIntersects(pt0.x, x, m_uCanvasWidth, m_nXCross, MAX_TRANVAS);
   nYIntersects = MIN (nYIntersects, MAX_TRANVAS);
   nXIntersects = MIN (nXIntersects, MAX_TRANVAS);

   m_uValidEnds = 0;
   m_ptStrokeEnds[m_uValidEnds++] = pt0;
   AddYCrossesToStrokeEnds(nYIntersects, &pt0, &pt1);
   AddXCrossesToStrokeEnds(nXIntersects, &pt0, &pt1);
   assert (m_uValidEnds < MAX_STROKE_ENDS);
   m_ptStrokeEnds[m_uValidEnds++] = pt1;

   g_bUp2Down = (pt0.y < pt1.y);
   g_bLeft2Right = (pt0.x < pt1.x);
   qsort (m_ptStrokeEnds, m_uValidEnds, sizeof(POINT), ComparePoints) ;
   RemoveSameStrokeEnds();
   CreateStrokes();
   m_uValidEnds = 0;
}

#define INIT_ANGLE 90

void CLogoExecutorNoWnd::DistanceToDelta (double distance, int* dx, int* dy)
{
   assert (dx && dy);
   double randian = (m_direction + INIT_ANGLE) * PI / 180;
   *dx = ROUND(distance * cos(randian));
   *dy = ROUND(-distance * sin(randian));
}

void CLogoExecutorNoWnd::AddLineStroke(POINT* pt0, POINT* pt1)
{
   m_ptCurrent = *pt1;   
   
   if (!m_bPenDown) {
      return;
   }

   CStroke* stk = new CStroke;
   if (!stk) {
      exit(0);
   }

   stk->m_line.from = *pt0;
   stk->m_line.to = *pt1;
   stk->m_line.pencolor = m_clrPen;
   stk->m_line.penwidth = m_uPenWidth;
   AddToStrokeList(stk);
}

void CLogoExecutorNoWnd::AddYCrossesToStrokeEnds(int nCount, POINT* p0, POINT* p1)
{
   int n = 0;
   assert (nCount >= 0);
   for (n = 0; n < nCount; n++) {
      assert (m_uValidEnds < MAX_STROKE_ENDS);
      int x = Y2X (p0, p1, m_nYCross[n]);
      m_ptStrokeEnds[m_uValidEnds].x = x;
      m_ptStrokeEnds[m_uValidEnds].y = m_nYCross[n];
      m_uValidEnds++;
   }
}

void CLogoExecutorNoWnd::AddXCrossesToStrokeEnds(int nCount, POINT* p0, POINT* p1)
{
   int n = 0;
   assert (nCount >= 0);
   for (n = 0; n < nCount; n++) {
      assert (m_uValidEnds < MAX_STROKE_ENDS);
      int y = X2Y (p0, p1, m_nXCross[n]);
      m_ptStrokeEnds[m_uValidEnds].y = y;
      m_ptStrokeEnds[m_uValidEnds].x = m_nXCross[n];
      m_uValidEnds++;
   }
}

void CLogoExecutorNoWnd::RemoveSameStrokeEnds()
{
   UINT i = 0;
   assert (m_uValidEnds >=2);
   POINT prev = m_ptStrokeEnds[0];
   i++;
   while (i < m_uValidEnds) {
      if (prev.x == m_ptStrokeEnds[i].x && prev.y == m_ptStrokeEnds[i].y) {
         if (i != m_uValidEnds -1) {
            assert (i < m_uValidEnds -1);
            memmove (m_ptStrokeEnds+i, m_ptStrokeEnds+i+1, sizeof(POINT)*(m_uValidEnds -i -1));
         }
         m_uValidEnds--;
         assert (m_uValidEnds);
         continue;
      }
      prev = m_ptStrokeEnds[i++];
   }
}

bool CLogoExecutorNoWnd::InSameArea(POINT* pt0, POINT*pt1)
{
   int nIntPart0, nIntPart1;
   assert (pt0 && pt1);
   nIntPart0 = IntDiv (pt0->x, m_uCanvasWidth);
   nIntPart1 = IntDiv (pt1->x, m_uCanvasWidth);
   if (nIntPart0 != nIntPart1) {
      return false;
   }
   nIntPart0 = IntDiv (pt0->y, m_uCanvasHeight);
   nIntPart1 = IntDiv (pt1->y, m_uCanvasHeight);
   if (nIntPart0 != nIntPart1) {
      return false;
   }
   return true;
}

void CLogoExecutorNoWnd::CreateStrokes()
{
   UINT i = 0;
   POINT pt0, pt1;
   assert (m_uValidEnds >=2);
   while ( i < m_uValidEnds) {
      if (( i < m_uValidEnds -1 && !InSameArea(&m_ptStrokeEnds[i], &m_ptStrokeEnds[i+1])) || 
          (i == m_uValidEnds -1) ) {
         WrapPoint (&m_ptStrokeEnds[i], &pt0);
         AddLineStroke (&pt0, &pt0);
         i ++;
         continue;
      } 
      assert (i < m_uValidEnds -1);
      WrapPoint (&m_ptStrokeEnds[i], &pt0);
      WrapPoint (&m_ptStrokeEnds[i+1], &pt1);
      AddLineStroke (&pt0, &pt1);
      i += 2;
   }
   return;
}

void CLogoExecutorNoWnd::WrapPoint (const POINT* ptInput, POINT* ptOutput)
{
   assert (ptInput && ptOutput);
   ptOutput->x = IntMod(ptInput->x, m_uCanvasWidth);
   ptOutput->y = IntMod(ptInput->y, m_uCanvasHeight);
}

int GetIntersects(int p0, int p1, int interval, int* arrIntersects, int nArrayLength)
{
   int start, end;
   int count = 0;

   assert (interval > 0);
   if (!arrIntersects) {
      assert (!nArrayLength);
   }

   bool bAscending = (p1 > p0);
   if (bAscending) {
      start = (p0 >= 0 ? (p0/interval +1)*interval : (p0/interval)*interval);
      end = (p1 >= 0 ? (p1/interval) * interval : (p1/interval-1)*interval);
      int i;
      for (i = start ; i <= end; i += interval) {
         if (i - 1 > p0 && i -1 < p1) {
            if (arrIntersects && count+1 <= nArrayLength) {
               arrIntersects[count] = i-1;
            }
            count++;
         }
         if (i > p0 && i < p1) {
            if (arrIntersects && count+1 <= nArrayLength) {
               arrIntersects[count] = i;
            }
            count++;
         } 
      }
   } else {
      start = (p0 >= 0 ? (p0/interval)*interval : (p0/interval-1)*interval);
      end = (p1 >=0 ? (p1/interval+1)*interval : (p1/interval)*interval);
      int i;
      for (i = start ; i >= end; i -= interval) {
         if (i < p0 && i > p1) {
            if (arrIntersects && count+1 <= nArrayLength) {
               arrIntersects[count++] = i;
            }
         }
         if (i -1 < p0 && i-1  > p1) {
            if (arrIntersects && count+1 <= nArrayLength) {
               arrIntersects[count++] = i-1;
            }
         } 
      }
   }
   return count;
}

int Y2X (POINT* p0, POINT* p1, int y)
{
   double x0, x1, y0, y1, x;
   x0 = p0->x;
   y0 = p0->y;
   x1 = p1->x;
   y1 = p1->y;
   assert (y0 != y1);
   x = x0 + (x1-x0)/(y1-y0) * ((double)y - y0);
   return ROUND(x);
}

int X2Y (POINT* p0, POINT* p1, int x)
{
   double x0, x1, y0, y1, y;
   x0 = p0->x;
   y0 = p0->y;
   x1 = p1->x;
   y1 = p1->y;
   assert (x0 != x1);
   y = y0 + (y1-y0)/(x1-x0) * ((double)x - x0);
   return ROUND(y);
}

int ComparePoints(const void* p0, const void* p1)
{
   POINT* pt0 = (POINT*) p0;
   POINT* pt1 = (POINT*) p1;

   int yDirection = (g_bUp2Down ? 1: -1);
   int xDirection = (g_bLeft2Right ? 1 : -1);
   int yResult = yDirection * (pt0->y - pt1->y);
   if (yResult) {
      return yResult;
   }
   int xResult = xDirection * (pt0->x - pt1->x);
   return xResult;
}

int IntDiv(int a, int b)
{
   assert (b > 0);
   if (a >= 0) {
      return a/b;
   }
   if (a/b*b == a) {
      return a/b;
   }
   return a/b-1;
}
